package com.maychat.websocket;

import com.maychat.config.Consts;
import com.maychat.enums.ContactType;
import com.maychat.enums.MessageType;
import com.maychat.mapper.ChatMessageMapper;
import com.maychat.mapper.ChatSessionUserMapper;
import com.maychat.mapper.UserContactApplyMapper;
import com.maychat.mapper.UserInfoMapper;
import com.maychat.pojo.ChatMessage;
import com.maychat.pojo.UserInfo;
import com.maychat.pojo.WSInitData;
import com.maychat.pojo.extended.ChatSessionComb;
import com.maychat.pojo.extended.Message;
import com.maychat.service.impl.RedisServiceImpl;
import com.maychat.utils.StringTools;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ChannelContextUtils {

    public static final ConcurrentHashMap<String, Channel> USER_CONTEXT_MAP = new ConcurrentHashMap<>();

    public static final ConcurrentHashMap<String, ChannelGroup> GROUP_CONTEXT_MAP = new ConcurrentHashMap<>();

    @Autowired
    private RedisServiceImpl redisService;
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Autowired
    private ChatSessionUserMapper chatSessionUserMapper;
    @Autowired
    private ChatMessageMapper chatMessageMapper;
    @Autowired
    private UserContactApplyMapper userContactApplyMapper;

    public void addContext(String userId, Channel channel) {
        String channelId = channel.id().toString();
        AttributeKey attributeKey;
        if (!AttributeKey.exists(channelId))
            attributeKey = AttributeKey.newInstance(channelId);
        else
            attributeKey = AttributeKey.valueOf(channelId);
        channel.attr(attributeKey).set(userId);
        List<String> ids = redisService.getUserContacts(userId);
        for (String id : ids) {
            if (id.startsWith(ContactType.GROUP.getPrefix()))
                add2Group(id, channel);
        }
        USER_CONTEXT_MAP.put(userId, channel);
        redisService.saveUserConnHeartBeat(userId);
        UserInfo updatedUserInfo = new UserInfo();
        updatedUserInfo.setUserId(userId);
        updatedUserInfo.setLastLoginTime(LocalDateTime.now());
        userInfoMapper.updateUserInfoById(updatedUserInfo);
        // 给用户发消息
        UserInfo userInfo = userInfoMapper.selectUserInfoById(userId);
        Long lastOfflineTime = userInfo.getLastOfflineTime();
        Long furthestQueryTime = lastOfflineTime;
        if (lastOfflineTime != null) {
            long l = System.currentTimeMillis() - Consts.TIME_MS_3DAYS;
            if (l > lastOfflineTime) furthestQueryTime = l;
        }
        List<ChatSessionComb> chats = chatSessionUserMapper.selectChatsByUid(userId);
        WSInitData wsInitData = new WSInitData();
        wsInitData.setChatList(chats);
        // 查询聊天信息
        List<String> targetContactIds =
                ids.stream().filter(id -> id.startsWith(ContactType.GROUP.getPrefix())).collect(Collectors.toList());
        targetContactIds.add(userId);
        List<ChatMessage> chatMessages = chatMessageMapper.selectCond(targetContactIds, furthestQueryTime);
        wsInitData.setChatMessageList(chatMessages);
        // 查询好友申请
        Integer count = userContactApplyMapper.selectCountCond(userId, furthestQueryTime);
        wsInitData.setApplyCount(count);
        //
        Message<WSInitData> msg = new Message<>();
        msg.setMessageType(MessageType.INIT.ordinal());
        msg.setContactId(userId);
        msg.setExtendedData(wsInitData);
        sendMsg(msg, userId);
    }

    public void sendMsg(Message<?> msg) {
        ContactType type = ContactType.getByPrefix(msg.getContactId());
        switch (type) {
            case GROUP -> send2Group(msg);
            case USER -> send2User(msg);
        }
    }

    public void send2User(Message<?> msg) {
        String contactId = msg.getContactId();
        if (StringUtils.isBlank(contactId)) return;
        sendMsg(msg, contactId);
        if (MessageType.FORCED_OFFLINE.ordinal() == msg.getMessageType()) {
            this.closeContext(contactId);
        }
    }

    public void send2Group(Message<?> msg) {
        String contactId = msg.getContactId();
        if (StringUtils.isBlank(contactId)) return;
        ChannelGroup channelGroup = GROUP_CONTEXT_MAP.get(msg.getContactId());
        if (channelGroup == null) return;
        channelGroup.writeAndFlush(new TextWebSocketFrame(StringTools.stringify(msg)));
        MessageType type = MessageType.getByOrdinal(msg.getMessageType());
        switch (type) {
            case LEAVE_GROUP, FORCED_LEAVE_GROUP -> {
                String userId = (String) msg.getExtendedData();
                redisService.removeUserContact(userId, msg.getContactId());
                Channel userChannel = USER_CONTEXT_MAP.get(userId);
                if (userChannel == null) return;
                channelGroup.remove(userChannel);
            }
            case DISSOLVE_GROUP -> {
                GROUP_CONTEXT_MAP.remove(msg.getContactId());
                channelGroup.close();
            }
        }
    }

    public void closeContext(String userId) {
        if (StringUtils.isBlank(userId)) return;
        redisService.clearUserToken(userId);
        Channel channel = USER_CONTEXT_MAP.get(userId);
        if (channel == null) return;
        channel.close();
    }

    protected void sendMsg(Message<?> msg, String receiveId) {
        if (receiveId == null) return;
        Channel userChannel = USER_CONTEXT_MAP.get(receiveId);
        if (userChannel == null) return;
        if (msg.getMessageType() == MessageType.ADD_FRIEND_SELF.ordinal()) {
            UserInfo userInfo = (UserInfo) msg.getExtendedData();
            msg.setMessageType(MessageType.ADD_FRIEND.ordinal());
            msg.setContactId(userInfo.getUserId());
            msg.setContactName(userInfo.getNickName());
            msg.setExtendedData(null);
        } else {
            msg.setContactId(msg.getSendUserId());
            msg.setContactName(msg.getSendUserNickName());
        }
        String jsonString = StringTools.stringify(msg);
        userChannel.writeAndFlush(new TextWebSocketFrame(jsonString));
    }

    public void add2Group(String groupId, Channel channel) {
        if (channel == null) return;
        ChannelGroup channelGroup = GROUP_CONTEXT_MAP.get(groupId);
        if (channelGroup == null) {
            channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            GROUP_CONTEXT_MAP.put(groupId, channelGroup);
        }
        channelGroup.add(channel);
    }

    public void addUser2Group(String userId, String groupId) {
        Channel channel = USER_CONTEXT_MAP.get(userId);
        if (channel == null) return;
        add2Group(groupId, channel);
    }


    public void removeContext(Channel channel) {
        Attribute<String> attr = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attr.get();
        if (!StringUtils.isBlank(userId)) {
            USER_CONTEXT_MAP.remove(userId);
        }
        redisService.clearUserConnHeartBeat(userId);
        UserInfo updated = new UserInfo();
        updated.setUserId(userId);
        updated.setLastOfflineTime(System.currentTimeMillis());
        userInfoMapper.updateUserInfoById(updated);

    }
}
